不論是登出還是中斷連線,在App中我最後都會希望回到登入畫面,因此在nav_main.xml中增加了Action:
<action
android:id="@+id/action_to_loginFragment"
app:destination="@id/loginFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@id/searchArticleFragment"
app:popUpToInclusive="true" />
此Action為Global Action,是放在navigation tag下而非fragment tag。
登出發生的時間點比較單純,預期是在SearchArticleFragment頁面點擊返回按鈕的時候,所以先用onBackPressedDispatcher攔截返回事件並在裡面跳出Dialog詢問是否登出:
// ...
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
AlertDialog.Builder(requireActivity())
.setTitle("登出")
.setMessage("是否登出Ptt?")
.setPositiveButton(android.R.string.ok) { _, _ ->
//TODO: Logout Process
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
})
// ...
// ...
val activity = (requireActivity() as MainActivity)
activity.showLoading("Logout...")
PttClient.getInstance().end()
activity.startPttClient {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
activity.dismissLoading()
if (it == 0) {
NavHostFragment.findNavController(this@SearchArticleFragment)
.navigate(R.id.action_to_loginFragment)
} else {
activity.showConnectionErrorDialog()
}
}
}
// ...
可以看到登出的部分我不打算走Ptt的指令流程。直接將WebSocket連線切斷後重連就好了。MainActivity.startPttClient是稍微改寫WelcomeFragment頁的程式碼,並將PttClient.expect的值做為callback回傳。
public val startPattern = arrayOf(
"請輸入代號,或以 guest 參觀,或以 new 註冊:"
)
public fun startPttClient(callback: ((Int) -> Unit)) {
lifecycleScope.launch(Dispatchers.IO) {
delay(500)
PttClient.getInstance().start()
callback(PttClient.getInstance().expect(startPattern))
}
}
這邊做完後WelcomeFragment使用到的地方也一起做了修改:
private fun startPttClient() {
if (checkOverlayDisplayPermission()) {
(requireActivity() as MainActivity).startPttClient {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
delay(500)
withContext(Dispatchers.Main) {
if (it == 0) {
val extras = FragmentNavigator.Extras.Builder()
.addSharedElement(binding.logo, "logo")
.build()
NavHostFragment.findNavController(this@WelcomeFragment)
.navigate(
R.id.action_welcomeFragment_to_loginFragment,
null,
null,
extras
)
} else {
(requireActivity() as MainActivity).showConnectionErrorDialog()
}
}
}
}
} else {
requestOverlayDisplayPermission()
}
}
連線中斷的模擬我是使用另外登入同帳號的方法。
companion object {
// ...
private var isClosedByUser = false
public var unexpectedClosedCallback: (() -> Unit)? = null
// ...
}
isClosedByUser flag是用來標註這次的onClose事件是否是發生在App主動斷線(如:登出)時:
public fun start() {
// ...
isClosedByUser = false
// ...
}
public fun end() {
isClosedByUser = true
// ...
}
並且在WebSocketClient的onClose中呼叫unexpectedClosedCallback
override fun onClose(code: Int, reason: String?, remote: Boolean) {
if (!isClosedByUser) {
unexpectedClosedCallback?.invoke()
}
}
PttClient.unexpectedClosedCallback = {
lifecycleScope.launch(Dispatchers.Main) {
AlertDialog.Builder(this@MainActivity)
.setTitle("Disconnected")
.setMessage("Reconnect to Ptt?")
.setPositiveButton(android.R.string.ok) { _, _ ->
showLoading("Reconnecting...")
PttClient.getInstance().end()
startPttClient {
lifecycleScope.launch(Dispatchers.Main) {
dismissLoading()
if (it == 0) {
findNavController(R.id.nav_host_fragment)
.navigate(R.id.action_to_loginFragment)
} else {
showConnectionErrorDialog()
}
}
}
}
.setNegativeButton(android.R.string.cancel) { _, _ -> finish() }
.setCancelable(false)
.show()
}
}
基本上就是走關閉→重啟→回到登入頁的流程,若不重連線或是再連線失敗的話就關閉App。
如果是在懸浮視窗中斷線的話,我選擇的做法是關閉Service並發送Notification告知使用者連線中斷。
以下內容寫在ObserveService的onCreate方法中:
PttClient.unexpectedClosedCallback = {
val manager = NotificationManagerCompat.from(this)
if (manager.getNotificationChannel(notificationChannelIdImportant) == null) {
val channel = NotificationChannelCompat.Builder(
notificationChannelIdImportant,
NotificationManager.IMPORTANCE_HIGH
).setName("CA_HIGH").build()
manager.createNotificationChannel(channel)
}
val builder = NotificationCompat.Builder(this, notificationChannelIdImportant)
.setSmallIcon(R.drawable.ic_outline_track)
.setContentTitle("Ptt connection has closed")
manager.notify(notificationIdImportant, builder.build())
stopSelf()
}
今天測試的時間比較長,所以使用了三倍速播放,看不清楚又很有興趣的話,就麻煩多看幾次囉...XD